package com.gc.cscott.run;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.BinaryCodec;
import org.apache.commons.io.IOUtils;

/**
 * Will run external program and chase it till timeout.
 *
 * @author root
 */
public class Chaser {
    private static final Logger log = Logger.getLogger(Chaser.class.getSimpleName());

    class MyOutStream extends ByteArrayOutputStream {
        final int limit = 1024*10;
        @Override
        public synchronized void write(int b) {
            if (count<limit){
                super.write(b);
            }
        }

        @Override
        public synchronized void write(byte[] b, int off, int len) {
            if (count<limit){
                super.write(b, off, len);
            }
        }
    }

    public ChaseResult chase(final Process process, final long timeout) {
        final ByteArrayOutputStream stdoutStream = new MyOutStream();
        new Thread() {
            @Override
            public void run() {
                try {
                    IOUtils.copy(process.getInputStream(), stdoutStream);
                } catch (IOException e) {
                    log.log(Level.SEVERE, "Error while collecting stdout", e);
                }
            }
        }.start();
        final ByteArrayOutputStream stderrStream = new MyOutStream();
        new Thread() {
            @Override
            public void run() {
                try {
                    IOUtils.copy(process.getErrorStream(), stderrStream);
                } catch (IOException e) {
                    log.log(Level.SEVERE, "Error while collecting syderr", e);
                }
            }
        }.start();

        final long start = System.currentTimeMillis();
        while (System.currentTimeMillis() - start < timeout) {
            try {
                Thread.sleep(25);
                final int exitCodeValue = process.exitValue();
                return new ChaseResult() {
                    {
                        setExecutionTime(System.currentTimeMillis() - start);
                        setTimeout(false);
                        setExitCode(exitCodeValue);
                        setStdout(new String(stdoutStream.toByteArray()));
                        setStderr(new String(stderrStream.toByteArray()));
                    }
                };
            } catch (InterruptedException ex) {
                log.severe("Interrupt ignored");
            } catch (IllegalThreadStateException notYetCompleted) {
                //ignore.
            }
        }
        process.destroy();
        return new ChaseResult() {
            {
                setExecutionTime(System.currentTimeMillis() - start);
                setTimeout(true);
                setExitCode(Integer.MAX_VALUE);

                setStdout(BinaryCodec.toAsciiString(stdoutStream.toByteArray()));
                setStderr(BinaryCodec.toAsciiString(stderrStream.toByteArray()));
            }
        };
    }

    public static class ChaseResult {
        long executionTime;

        boolean timeout;

        int exitCode;

        String stdout;

        String stderr;

        public boolean isTimeout() {
            return timeout;
        }

        public void setTimeout(boolean timeout) {
            this.timeout = timeout;
        }

        public long getExecutionTime() {
            return executionTime;
        }

        public void setExecutionTime(long executionTime) {
            this.executionTime = executionTime;
        }

        public int getExitCode() {
            return exitCode;
        }

        public void setExitCode(int exitCode) {
            this.exitCode = exitCode;
        }

        public String getStderr() {
            return stderr;
        }

        public void setStderr(String stderr) {
            this.stderr = stderr;
        }

        public String getStdout() {
            return stdout;
        }

        public void setStdout(String stdout) {
            this.stdout = stdout;
        }
    }
}
